﻿using Microsoft.EntityFrameworkCore;

namespace AnalyStock.DataManage;

/// <summary>
///     通用数据处理模板
/// </summary>
/// <typeparam name="T"></typeparam>

#region

internal interface IDbTemplate<T>
{
    Task<IList<T>> QueryDataAsync(params string[] args);
    Task<int> SaveDataAsync(SaveType saveType, IList<T> list, string stockcode = default);
    Task<int> DeleDataAsync(IList<T> list, string stockcode = default);
}

internal class DbTemplate<T> : SqiltTableTemplate<T>, IDbTemplate<T> where T : class
{
    public virtual Task<IList<T>> QueryDataAsync(params string[] args)
    {
        return QueryDataTemplateAsync();
    }

    //不能使用 asny void 会导致错误异常点在异步发生，导致无法捕获。    
    public Task<int> SaveDataAsync(SaveType saveType, IList<T> list, string stockCode = default)
    {
        return saveType switch
        {
            SaveType.Insert => InsertDataTemplateAsync(list, stockCode),
            SaveType.Modify => ModifyDataTemplateAsync(list),
            SaveType.ReWrite => ReWriteDataTemplateAsync(list),
            _ => throw new ArgumentNullException()
        };
    }

    public Task<int> DeleDataAsync(IList<T> list, string stockCode = default)
    {
        return DelDataTemplateAsync(list, stockCode);
    }
}

#endregion

/// <summary>
///     泛型数据库结构及方法模板
/// </summary>

#region

internal class SqliteContextTemplate<T> : DbContext where T : class
{
    private readonly string ConnString;

    public SqliteContextTemplate(string stockCode = default)
    {
        var path = stockCode is "" or null
            ? ApplicationParam.BasicDbPath
            : $"{ApplicationParam.DailyDbPath}{stockCode}.db3";
        ConnString = $"Data Source={path}";
    }

    public DbSet<T> QTable { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        _ = optionsBuilder.UseSqlite(ConnString);
    }
}

internal class SqiltTableTemplate<T> where T : class
{
    internal static async Task<IList<T>> QueryDataTemplateAsync<Torder>
        (string stockcode, Expression<Func<T, bool>> expWhere, Expression<Func<T, Torder>> expOrder)
    {
        await using var dbSqlite = new SqliteContextTemplate<T>(stockcode);
        return await dbSqlite.QTable.Where(expWhere)
                                    .OrderBy(expOrder)
                                    .AsNoTracking()
                                    .ToListAsync()
                                    .ConfigureAwait(false);
    }

    internal static async Task<IList<T>> QueryDataTemplateAsync<Torder>
        (Expression<Func<T, Torder>> expOrder)
    {
        await using var dbSqlite = new SqliteContextTemplate<T>();
        return await dbSqlite.QTable.OrderBy(expOrder)
                                    .AsNoTracking()
                                    .ToListAsync()
                                    .ConfigureAwait(false);
    }

    internal static async Task<IList<T>> QueryDataTemplateAsync()
    {
        await using var dbsqlite = new SqliteContextTemplate<T>();
        return await dbsqlite.QTable.AsNoTracking()
                                    .ToListAsync()
                                    .ConfigureAwait(false);
    }

    //stockCode参数主要是确认数据文件的路径及文件名
    internal static async Task<int> InsertDataTemplateAsync(IList<T> list, string stockCode)
    {
        //判断数据文件是否存在，不存在就并创建数据库文件
        _ = await IsHaveTableAsync(stockCode).ConfigureAwait(false);
        //执行保存数据
        await using var dbSqlite = new SqliteContextTemplate<T>(stockCode);
        foreach (var item in list)
        {
            if (!await dbSqlite.QTable.ContainsAsync(item).ConfigureAwait(false))
            {
                await dbSqlite.QTable.AddAsync(item).ConfigureAwait(false);
            }
        }
        return await dbSqlite.SaveChangesAsync().ConfigureAwait(false);

        static async Task<bool> IsHaveTableAsync(string stockCode)
        {
            //判断并，创建数据库，包括所有实体Table
            await using var dbSqlite = new SqliteContext(stockCode);
            return await dbSqlite.Database.EnsureCreatedAsync().ConfigureAwait(false);
        }
    }

    internal static async Task<int> ModifyDataTemplateAsync(IList<T> list)
    {
        await using var dbSqlite = new SqliteContextTemplate<T>();
        foreach (var item in list)
        {
            if (!await dbSqlite.QTable.ContainsAsync(item).ConfigureAwait(false))
            {
                await dbSqlite.QTable.AddAsync(item).ConfigureAwait(false);
            }
            else
            {
                dbSqlite.QTable.Entry(item).State = EntityState.Modified;
            }
        }

        return await dbSqlite.SaveChangesAsync().ConfigureAwait(false);
    }

    internal static async Task<int> DelDataTemplateAsync(IList<T> list, string stockCode)
    {
        await using var dbSqlite = new SqliteContextTemplate<T>(stockCode);
        foreach (var item in list)
        {
            dbSqlite.QTable.Entry(item).State = EntityState.Deleted;
        }

        return await dbSqlite.SaveChangesAsync().ConfigureAwait(false);
    }

    internal static async Task<int> ReWriteDataTemplateAsync(IList<T> list)
    {
        await using var dbSqlite = new SqliteContextTemplate<T>();
        // await dbSqlite.QTable.ForEachAsync(n =>
        //       dbSqlite.QTable.Entry(n).State = EntityState.Deleted
        // ).ConfigureAwait(false);
        await dbSqlite.QTable.ExecuteDeleteAsync().ConfigureAwait(false);
        await dbSqlite.QTable.AddRangeAsync(list).ConfigureAwait(false);
        return await dbSqlite.SaveChangesAsync().ConfigureAwait(false);
    }
}

#endregion

//数据库实体结构类，临时创建Db文件时需要实体结构
internal class SqliteContext : DbContext
{
    private readonly string ConnString;

    public SqliteContext(string stockCode = default)
    {
        var path = stockCode is null
            ? ApplicationParam.BasicDbPath
            : $"{ApplicationParam.DailyDbPath}{stockCode}.db3";
        ConnString = $"Data Source={path}";
    }

    public DbSet<Daily> Dailies { get; set; }
    public DbSet<FinancialIndex> MainFinancialIndices { get; set; }
    public DbSet<StockBasic> StockBasics { get; set; }
    public DbSet<BakDaily> BakDailies { get; set; }
    public DbSet<SelectStock> SelectStocks { get; set; }
    public DbSet<Conception> Conceptions { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        _ = optionsBuilder.UseSqlite(ConnString);
    }
}

//泛型实类，多态实现实类查询数据返回
internal class DbQuery_Daily : DbTemplate<Daily>
{
    public override Task<IList<Daily>> QueryDataAsync(params string[] args)
    {
        return QueryDataTemplateAsync(args[0],
            p => p.Trade_date.CompareTo(args[1]) >= 0 && p.Trade_date.CompareTo(args[2]) <= 0,
            p => p.Trade_date);
    }
}

internal class DbQuery_FinancialIndex : DbTemplate<FinancialIndex>
{
    public override Task<IList<FinancialIndex>> QueryDataAsync(params string[] args)
    {
        return QueryDataTemplateAsync(args[0],
            p => p.ReportDt.CompareTo(args[1]) >= 0 && p.ReportDt.CompareTo(args[2]) <= 0,
            p => p.ReportDt);
    }
}

internal class DbQuery_StockInfo : DbTemplate<StockInfo>
{
    public override async Task<IList<StockInfo>> QueryDataAsync(params string[] args)
    {
        var queryBakDaily = await SqiltTableTemplate<BakDaily>.QueryDataTemplateAsync()
            .ConfigureAwait(false);
        var queryBasicStock = await SqiltTableTemplate<StockBasic>.QueryDataTemplateAsync()
            .ConfigureAwait(false);
        var querySeleStock = await SqiltTableTemplate<SelectStock>.QueryDataTemplateAsync()
            .ConfigureAwait(false);
        var mergeStockInfo = from bkdata in queryBakDaily
                             join bsdata in queryBasicStock
                                 on bkdata.Ts_code equals bsdata.Ts_code into group1
                             from m1 in group1.DefaultIfEmpty(new StockBasic()) //没有左联上，取默认值，右边主键为空
                             join seledata in querySeleStock
                                 on bkdata.Ts_code equals seledata.Ts_code into group2
                             from m2 in group2.DefaultIfEmpty(new SelectStock())
                             select new StockInfo
                             {
                                 Ts_code = bkdata.Ts_code,
                                 Name = bkdata.Name,
                                 Pe = bkdata.Pe,
                                 Float_mv = bkdata.Float_mv,
                                 Total_mv = bkdata.Total_mv,
                                 Total_share = bkdata.Total_share,
                                 Float_share = bkdata.Float_share,
                                 Area = m1.Area,
                                 Market = m1.Market,
                                 Industry = m1.Industry,
                                 Conception = m2.Conception,
                                 Turn_over = bkdata.Turn_over,
                                 Vol_ratio = bkdata.Vol_ratio,
                                 Issele = !string.IsNullOrEmpty(m2.Name),
                                 Trade_date = bkdata.Trade_date,
                                 Note = m2.Note
                             };
        return mergeStockInfo.ToList();
    }
}

internal class DbQuery_SelectStock : DbTemplate<SelectStock>
{
    public override async Task<IList<SelectStock>> QueryDataAsync(params string[] args)
    {
        return await QueryDataTemplateAsync(n => n.OrderID).ConfigureAwait(false);
    }
}

internal class DbQuery_Conception : DbTemplate<Conception>
{
    public override async Task<IList<Conception>> QueryDataAsync(params string[] args)
    {
        return await QueryDataTemplateAsync(n => n.OrderID).ConfigureAwait(false);
    }
}